ErschlieĂen Sie das Potenzial des Python-'email'-Pakets. Lernen Sie, komplexe MIME-Nachrichten zu erstellen und eingehende E-Mails zur Datenextraktion effektiv und global zu parsen.
Das Python E-Mail-Paket beherrschen: Die Kunst der MIME-Nachrichtenkonstruktion und des robusten Parsings
E-Mail ist nach wie vor ein Eckpfeiler der globalen Kommunikation, unerlĂ€sslich fĂŒr persönliche Korrespondenz, GeschĂ€ftsablĂ€ufe und automatisierte Systembenachrichtigungen. Hinter jeder Rich-Text-E-Mail, jedem Anhang und jeder sorgfĂ€ltig formatierten Signatur verbirgt sich die KomplexitĂ€t der Multipurpose Internet Mail Extensions (MIME). FĂŒr Entwickler, insbesondere fĂŒr diejenigen, die mit Python arbeiten, ist die Beherrschung der programmatischen Erstellung und des Parsens dieser MIME-Nachrichten eine entscheidende FĂ€higkeit.
Pythons integriertes email
-Paket bietet ein robustes und umfassendes Framework fĂŒr die Verarbeitung von E-Mail-Nachrichten. Es dient nicht nur dem Senden einfacher Texte; es wurde entwickelt, um die komplizierten Details von MIME zu abstrahieren, sodass Sie anspruchsvolle E-Mails erstellen und spezifische Daten aus eingehenden E-Mails mit bemerkenswerter PrĂ€zision extrahieren können. Dieser Leitfaden fĂŒhrt Sie tief in die beiden Hauptaspekte dieses Pakets ein: die Erstellung von MIME-Nachrichten zum Senden und das Parsen derselben zur Datenextraktion, wobei eine globale Perspektive auf bewĂ€hrte Verfahren geboten wird.
Das VerstĂ€ndnis von sowohl der Erstellung als auch des Parsens ist entscheidend. Wenn Sie eine Nachricht erstellen, definieren Sie im Wesentlichen ihre Struktur und ihren Inhalt, damit ein anderes System sie interpretieren kann. Wenn Sie parsen, interpretieren Sie eine Struktur und einen Inhalt, die von einem anderen System definiert wurden. Ein tiefes VerstĂ€ndnis des einen hilft sehr bei der Beherrschung des anderen und fĂŒhrt zu widerstandsfĂ€higeren und interoperableren E-Mail-Anwendungen.
MIME verstehen: Das RĂŒckgrat moderner E-Mails
Bevor wir uns den Python-Spezifika widmen, ist es wichtig zu verstehen, was MIME ist und warum es so entscheidend ist. UrsprĂŒnglich waren E-Mail-Nachrichten auf reinen Text (7-Bit-ASCII-Zeichen) beschrĂ€nkt. MIME, eingefĂŒhrt in den frĂŒhen 1990er Jahren, erweiterte die FĂ€higkeiten von E-Mails, um Folgendes zu unterstĂŒtzen:
- Nicht-ASCII-Zeichen: Ermöglicht Text in Sprachen wie Arabisch, Chinesisch, Russisch oder jeder anderen Sprache, die Zeichen auĂerhalb des ASCII-Satzes verwendet.
- AnhÀnge: Das Senden von Dateien wie Dokumenten, Bildern, Audio und Video.
- Rich-Text-Formatierung: HTML-E-Mails mit Fett-, Kursivschrift, Farben und Layouts.
- Mehrere Teile: Die Kombination von reinem Text, HTML und AnhÀngen in einer einzigen E-Mail.
MIME erreicht dies durch das HinzufĂŒgen spezifischer Header zu einer E-Mail-Nachricht und die Strukturierung ihres Körpers in verschiedene "Teile". Wichtige MIME-Header, auf die Sie stoĂen werden, sind:
Content-Type:
Spezifiziert den Datentyp in einem Teil (z. B.text/plain
,text/html
,image/jpeg
,application/pdf
,multipart/alternative
). Er enthÀlt oft auch einencharset
-Parameter (z. B.charset=utf-8
).Content-Transfer-Encoding:
Gibt an, wie der E-Mail-Client des EmpfÀngers den Inhalt dekodieren soll (z. B.base64
fĂŒr binĂ€re Daten,quoted-printable
fĂŒr hauptsĂ€chlich Text mit einigen Nicht-ASCII-Zeichen).Content-Disposition:
SchlÀgt vor, wie der E-Mail-Client des EmpfÀngers den Teil anzeigen soll (z. B.inline
zur Anzeige innerhalb des Nachrichtentextes,attachment
fĂŒr eine zu speichernde Datei).
Das Python email
-Paket: Eine tiefgehende Betrachtung
Pythons email
-Paket ist eine umfassende Bibliothek, die fĂŒr die programmatische Erstellung, das Parsen und die Ănderung von E-Mail-Nachrichten entwickelt wurde. Es basiert auf dem Konzept von Message
-Objekten, die die Struktur einer E-Mail reprÀsentieren.
Wichtige Module innerhalb des Pakets sind:
email.message:
EnthÀlt die KernklasseEmailMessage
, die die primÀre Schnittstelle zur Erstellung und Bearbeitung von E-Mail-Nachrichten darstellt. Es ist eine sehr flexible Klasse, die MIME-Details automatisch handhabt.email.mime:
Bietet Àltere Klassen (wieMIMEText
,MIMEMultipart
), die eine explizitere Kontrolle ĂŒber die MIME-Struktur ermöglichen. ObwohlEmailMessage
aufgrund seiner Einfachheit fĂŒr neuen Code im Allgemeinen bevorzugt wird, kann das VerstĂ€ndnis dieser Klassen von Vorteil sein.email.parser:
Bietet Klassen wieBytesParser
undParser
, um rohe E-Mail-Daten (Bytes oder Strings) inEmailMessage
-Objekte umzuwandeln.email.policy:
Definiert Richtlinien (Policies), die steuern, wie E-Mail-Nachrichten erstellt und geparst werden, und beeinflusst dabei die Header-Kodierung, Zeilenenden und Fehlerbehandlung.
FĂŒr die meisten modernen AnwendungsfĂ€lle interagieren Sie hauptsĂ€chlich mit der email.message.EmailMessage
-Klasse, sowohl fĂŒr die Erstellung als auch als geparstes Nachrichtenobjekt. Ihre Methoden vereinfachen den Prozess erheblich, der frĂŒher mit den Ă€lteren email.mime
-Klassen umstÀndlicher war.
MIME-Nachrichtenkonstruktion: E-Mails mit PrÀzision erstellen
Die Erstellung von E-Mails umfasst das ZusammenfĂŒgen verschiedener Komponenten (Text, HTML, AnhĂ€nge) zu einer gĂŒltigen MIME-Struktur. Die EmailMessage
-Klasse rationalisiert diesen Prozess erheblich.
Einfache Text-E-Mails
Die einfachste E-Mail ist reiner Text. Sie können eine erstellen und grundlegende Header mĂŒhelos festlegen:
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Greetings from Python'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Hello, this is a plain text email sent from Python.\n\nBest regards,\nYour Python Script')
print(msg.as_string())
ErklÀrung:
EmailMessage()
erstellt ein leeres Nachrichtenobjekt.- Der wörterbuchÀhnliche Zugriff (
msg['Subject'] = ...
) setzt allgemeine Header. set_content()
fĂŒgt den Hauptinhalt der E-Mail hinzu. StandardmĂ€Ăig leitet esContent-Type: text/plain; charset="utf-8"
ab.as_string()
serialisiert die Nachricht in ein String-Format, das zum Senden per SMTP oder zum Speichern in einer Datei geeignet ist.
HinzufĂŒgen von HTML-Inhalten
Um eine HTML-E-Mail zu senden, geben Sie einfach den Inhaltstyp beim Aufruf von set_content()
an. Es ist eine gute Praxis, eine reine Textalternative fĂŒr EmpfĂ€nger bereitzustellen, deren E-Mail-Clients HTML nicht rendern, oder aus GrĂŒnden der Barrierefreiheit.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Your HTML Newsletter'
msg['From'] = 'newsletter@example.com'
msg['To'] = 'subscriber@example.com'
html_content = """
<html>
<head></head>
<body>
<h1>Welcome to Our Global Update!</h1>
<p>Dear Subscriber,</p>
<p>This is your <strong>latest update</strong> from around the world.</p>
<p>Visit our <a href="http://www.example.com">website</a> for more.</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
"""
# Add the HTML version
msg.add_alternative(html_content, subtype='html')
# Add a plain text fallback
plain_text_content = (
"Welcome to Our Global Update!\n\n"
"Dear Subscriber,\n\n"
"This is your latest update from around the world.\n"
"Visit our website for more: http://www.example.com\n\n"
"Best regards,\nThe Team"
)
msg.add_alternative(plain_text_content, subtype='plain')
print(msg.as_string())
ErklÀrung:
add_alternative()
wird verwendet, um verschiedene Darstellungen des *gleichen* Inhalts hinzuzufĂŒgen. Der E-Mail-Client zeigt die "beste" an, die er verarbeiten kann (normalerweise HTML).- Dies erstellt automatisch eine
multipart/alternative
MIME-Struktur.
Umgang mit AnhÀngen
Das AnhÀngen von Dateien ist mit add_attachment()
unkompliziert. Sie können jede Art von Datei anhĂ€ngen, und das Paket kĂŒmmert sich um die entsprechenden MIME-Typen und Kodierungen (normalerweise base64
).
from email.message import EmailMessage
from pathlib import Path
# Create dummy files for demonstration
Path('report.pdf').write_bytes(b'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n2 0 obj<</Count 0>>endobj\nxref\n0 3\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\ntrailer<</Size 3/Root 1 0 R>>startxref\n104\n%%EOF') # A very basic, invalid PDF placeholder
Path('logo.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82') # A 1x1 transparent PNG placeholder
msg = EmailMessage()
msg['Subject'] = 'Important Document and Image'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Please find the attached report and company logo.')
# Attach a PDF file
with open('report.pdf', 'rb') as f:
file_data = f.read()
msg.add_attachment(
file_data,
maintype='application',
subtype='pdf',
filename='Annual_Report_2024.pdf'
)
# Attach an image file
with open('logo.png', 'rb') as f:
image_data = f.read()
msg.add_attachment(
image_data,
maintype='image',
subtype='png',
filename='CompanyLogo.png'
)
print(msg.as_string())
# Clean up dummy files
Path('report.pdf').unlink()
Path('logo.png').unlink()
ErklÀrung:
add_attachment()
nimmt die rohen Bytes des Dateiinhalts entgegen.maintype
undsubtype
spezifizieren den MIME-Typ (z. B.application/pdf
,image/png
). Diese sind entscheidend, damit der E-Mail-Client des EmpfÀngers den Anhang korrekt identifizieren und verarbeiten kann.filename
gibt den Namen an, unter dem der Anhang vom EmpfÀnger gespeichert wird.- Dies richtet automatisch eine
multipart/mixed
-Struktur ein.
Erstellen von Multipart-Nachrichten
Wenn Sie eine Nachricht mit einem HTML-Text, einer reinen Text-Alternative und eingebetteten Bildern oder zugehörigen Dateien haben, benötigen Sie eine komplexere Multipart-Struktur. Die EmailMessage
-Klasse handhabt dies intelligent mit add_related()
und add_alternative()
.
Ein hÀufiges Szenario ist eine HTML-E-Mail mit einem direkt in das HTML eingebetteten Bild (ein "Inline"-Bild). Dies verwendet multipart/related
.
from email.message import EmailMessage
from pathlib import Path
# Create a dummy image file for demonstration (a 1x1 transparent PNG)
Path('banner.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82')
msg = EmailMessage()
msg['Subject'] = 'Inline Image Example'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
# Plain text version (fallback)
plain_text = 'Check out our amazing banner!\n\n[Image: Banner.png]\n\nVisit our site.'
msg.set_content(plain_text, subtype='plain') # Set initial plain text content
# HTML version (with CID for inline image)
html_content = """
<html>
<head></head>
<body>
<h1>Our Latest Offer!</h1>
<p>Dear Customer,</p>
<p>Don't miss out on our special global promotion:</p>
<img src="cid:my-banner-image" alt="Promotion Banner">
<p>Click <a href="http://www.example.com">here</a> to learn more.</p>
</body>
</html>
"""
msg.add_alternative(html_content, subtype='html') # Add HTML alternative
# Add the inline image (related content)
with open('banner.png', 'rb') as img_file:
image_data = img_file.read()
msg.add_related(
image_data,
maintype='image',
subtype='png',
cid='my-banner-image' # This CID matches the 'src' in HTML
)
print(msg.as_string())
# Clean up dummy file
Path('banner.png').unlink()
ErklÀrung:
set_content()
legt den ursprĂŒnglichen Inhalt fest (hier reiner Text).add_alternative()
fĂŒgt die HTML-Version hinzu und erstellt einemultipart/alternative
-Struktur, die die reinen Text- und HTML-Teile enthÀlt.add_related()
wird fĂŒr Inhalte verwendet, die mit einem der Nachrichtenteile "verwandt" sind, typischerweise Inline-Bilder in HTML. Es verwendet einencid
-(Content-ID)-Parameter, auf den dann im HTML-Tag<img src="cid:my-banner-image">
verwiesen wird.- Die endgĂŒltige Struktur ist
multipart/mixed
(wenn es externe AnhÀnge gÀbe), die einenmultipart/alternative
-Teil enthÀlt, der wiederum einenmultipart/related
-Teil enthÀlt. Dermultipart/related
-Teil enthÀlt das HTML und das Inline-Bild. DieEmailMessage
-Klasse ĂŒbernimmt diese VerschachtelungskomplexitĂ€t fĂŒr Sie.
Kodierung und ZeichensĂ€tze fĂŒr globale Reichweite
FĂŒr die internationale Kommunikation ist eine korrekte Zeichenkodierung von gröĂter Bedeutung. Das email
-Paket ist standardmĂ€Ăig sehr darauf bedacht, UTF-8 zu verwenden, was der universelle Standard fĂŒr die Handhabung verschiedener ZeichensĂ€tze aus der ganzen Welt ist.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Global Characters: ăăă«ăĄăŻ, ĐŃĐžĐČĐ”Ń, à€šà€źà€žà„à€€à„'
msg['From'] = 'global_sender@example.com'
msg['To'] = 'global_recipient@example.com'
# Japanese, Russian, and Hindi characters
content = "This message contains diverse global characters:\n"
content += "ăăă«ăĄăŻ (Japanese)\n"
content += "ĐŃĐžĐČĐ”Ń (Russian)\n"
content += "à€šà€źà€žà„à€€à„ (Hindi)\n\n"
content += "The 'email' package handles UTF-8 gracefully."
msg.set_content(content)
print(msg.as_string())
ErklÀrung:
- Wenn
set_content()
einen Python-String erhÀlt, kodiert es ihn automatisch in UTF-8 und setzt denContent-Type: text/plain; charset="utf-8"
-Header. - Wenn der Inhalt es erfordert (z. B. viele Nicht-ASCII-Zeichen enthÀlt), kann es auch
Content-Transfer-Encoding: quoted-printable
oderbase64
anwenden, um eine sichere Ăbertragung ĂŒber Ă€ltere E-Mail-Systeme zu gewĂ€hrleisten. Das Paket handhabt dies automatisch gemÀà der gewĂ€hlten Richtlinie.
Benutzerdefinierte Header und Richtlinien
Sie können jeden benutzerdefinierten Header zu einer E-Mail hinzufĂŒgen. Richtlinien (aus email.policy
) definieren, wie Nachrichten behandelt werden, und beeinflussen Aspekte wie Header-Kodierung, Zeilenenden und Fehlerbehandlung. Die Standardrichtlinie ist im Allgemeinen gut, aber Sie können SMTP
fĂŒr strenge SMTP-KonformitĂ€t wĂ€hlen oder benutzerdefinierte definieren.
from email.message import EmailMessage
from email import policy
msg = EmailMessage(policy=policy.SMTP)
msg['Subject'] = 'Email with Custom Header'
msg['From'] = 'info@example.org'
msg['To'] = 'user@example.org'
msg['X-Custom-Header'] = 'This is a custom value for tracking'
msg['Reply-To'] = 'support@example.org'
msg.set_content('This email demonstrates custom headers and policies.')
print(msg.as_string())
ErklÀrung:
- Die Verwendung von
policy=policy.SMTP
gewĂ€hrleistet eine strikte Einhaltung der SMTP-Standards, was fĂŒr die Zustellbarkeit entscheidend sein kann. - Benutzerdefinierte Header werden genauso wie Standard-Header hinzugefĂŒgt. Sie beginnen oft mit
X-
, um nicht standardisierte Header zu kennzeichnen.
MIME-Nachrichten parsen: Informationen aus eingehenden E-Mails extrahieren
Beim Parsen werden rohe E-Mail-Daten (typischerweise ĂŒber IMAP oder aus einer Datei empfangen) in ein `EmailMessage`-Objekt umgewandelt, das Sie dann leicht inspizieren und bearbeiten können.
Laden und initiales Parsen
Sie erhalten E-Mails normalerweise als rohe Bytes. Der email.parser.BytesParser
(oder die Hilfsfunktionen email.message_from_bytes()
) wird dafĂŒr verwendet.
from email.parser import BytesParser
from email.policy import default
raw_email_bytes = b"""
From: sender@example.com
To: recipient@example.com
Subject: Test Email with Basic Headers
Date: Mon, 1 Jan 2024 10:00:00 +0000
Content-Type: text/plain; charset="utf-8"
This is the body of the email.
It's a simple test.
"""
# Using BytesParser
parser = BytesParser(policy=default)
msg = parser.parsebytes(raw_email_bytes)
# Or using the convenience function
# from email import message_from_bytes
# msg = message_from_bytes(raw_email_bytes, policy=default)
print(f"Subject: {msg['subject']}")
print(f"From: {msg['from']}")
print(f"Content-Type: {msg['Content-Type']}")
ErklÀrung:
BytesParser
nimmt rohe Byte-Daten (so wie E-Mails ĂŒbertragen werden) und gibt einEmailMessage
-Objekt zurĂŒck.policy=default
spezifiziert die Parsing-Regeln.
Zugriff auf Header
Header sind ĂŒber wörterbuchĂ€hnliche SchlĂŒssel leicht zugĂ€nglich. Das Paket kĂŒmmert sich automatisch um die Dekodierung von kodierten Headern (z. B. Betreffzeilen mit internationalen Zeichen).
# ... (using the 'msg' object from the previous parsing example)
print(f"Date: {msg['date']}")
print(f"Message ID: {msg['Message-ID'] if 'Message-ID' in msg else 'N/A'}")
# Handling multiple headers (e.g., 'Received' headers)
# from email.message import EmailMessage # If not imported yet
# from email import message_from_string # For a quick string example
multi_header_email = message_from_string(
"""
From: a@example.com
To: b@example.com
Subject: Multi-header Test
Received: from client.example.com (client.example.com [192.168.1.100])
by server.example.com (Postfix) with ESMTP id 123456789
for <b@example.com>; Mon, 1 Jan 2024 10:00:00 +0000 (GMT)
Received: from mx.another.com (mx.another.com [192.168.1.101])
by server.example.com (Postfix) with ESMTP id 987654321
for <b@example.com>; Mon, 1 Jan 2024 09:59:00 +0000 (GMT)
Body content here.
"""
)
received_headers = multi_header_email.get_all('received')
if received_headers:
print("\nReceived Headers:")
for header in received_headers:
print(f"- {header}")
ErklÀrung:
- Der Zugriff auf einen Header gibt seinen Wert als String zurĂŒck.
get_all('header-name')
ist nĂŒtzlich fĂŒr Header, die mehrmals vorkommen können (wieReceived
).- Das Paket ĂŒbernimmt die Dekodierung von Headern, sodass Werte wie
Subject: =?utf-8?Q?Global_Characters:_=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF?=
automatisch in lesbare Strings umgewandelt werden.
Extrahieren des E-Mail-Textes
Das Extrahieren des eigentlichen Nachrichtentextes erfordert die PrĂŒfung, ob die Nachricht multipart ist. Bei Multipart-Nachrichten iteriert man durch ihre Teile.
from email.message import EmailMessage
from email import message_from_string
multipart_email_raw = """
From: multi@example.com
To: user@example.com
Subject: Test Multipart Email
Content-Type: multipart/alternative; boundary="_----------=_12345"
--_----------=_12345
Content-Type: text/plain; charset="utf-8"
Hello from the plain text part!
--_----------=_12345
Content-Type: text/html; charset="utf-8"
<html>
<body>
<h1>Hello from the HTML part!</h1>
<p>This is a <strong>rich text</strong> email.</p>
</body>
</html>
--_----------=_12345--
"""
msg = message_from_string(multipart_email_raw)
if msg.is_multipart():
print("\n--- Multipart Email Body ---")
for part in msg.iter_parts():
content_type = part.get_content_type()
charset = part.get_content_charset() or 'utf-8' # Default to utf-8 if not specified
payload = part.get_payload(decode=True) # Decode payload bytes
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {content_type}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content-Type: {content_type}, Charset: {charset}\nContent: (Binary or undecodable data)\n")
# Handle binary data, or attempt a fallback encoding
else:
print("\n--- Single Part Email Body ---")
charset = msg.get_content_charset() or 'utf-8'
payload = msg.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {msg.get_content_type()}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content: (Binary or undecodable data)\n")
ErklÀrung:
is_multipart()
bestimmt, ob die E-Mail mehrere Teile hat.iter_parts()
iteriert durch alle Unterteile einer Multipart-Nachricht.get_content_type()
gibt den vollstĂ€ndigen MIME-Typ zurĂŒck (z. B.text/plain
).get_content_charset()
extrahiert den Zeichensatz aus demContent-Type
-Header.get_payload(decode=True)
ist entscheidend: Es gibt den *dekodierten* Inhalt als Bytes zurĂŒck. Sie mĂŒssen diese Bytes dann mit dem korrekten Zeichensatz.decode()
, um einen Python-String zu erhalten.
Umgang mit AnhÀngen beim Parsen
AnhÀnge sind ebenfalls Teile einer Multipart-Nachricht. Sie können sie anhand ihres Content-Disposition
-Headers identifizieren und ihren dekodierten Payload speichern.
from email.message import EmailMessage
from email import message_from_string
import os
# Example email with a simple attachment
email_with_attachment = """
From: attach@example.com
To: user@example.com
Subject: Document Attached
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_----------=_XYZ"
--_----------=_XYZ
Content-Type: text/plain; charset="utf-8"
Here is your requested document.
--_----------=_XYZ
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="document.pdf"
JVBERi0xLjQKMSAwIG9iagpbL1BERi9UZXh0L0ltYWdlQy9JbWFnZUkvSW1hZ0VCXQplbmRvYmoK
--_----------=_XYZ--
"""
msg = message_from_string(email_with_attachment)
output_dir = 'parsed_attachments'
os.makedirs(output_dir, exist_ok=True)
print("\n--- Processing Attachments ---")
for part in msg.iter_attachments():
filename = part.get_filename()
if filename:
filepath = os.path.join(output_dir, filename)
try:
with open(filepath, 'wb') as f:
f.write(part.get_payload(decode=True))
print(f"Saved attachment: {filepath} (Type: {part.get_content_type()})")
except Exception as e:
print(f"Error saving {filename}: {e}")
else:
print(f"Found an attachment without a filename (Content-Type: {part.get_content_type()})")
# Clean up the output directory
# import shutil
# shutil.rmtree(output_dir)
ErklÀrung:
iter_attachments()
liefert speziell die Teile, die wahrscheinlich AnhÀnge sind (d. h. einenContent-Disposition: attachment
-Header haben oder nicht anderweitig klassifiziert sind).get_filename()
extrahiert den Dateinamen aus demContent-Disposition
-Header.part.get_payload(decode=True)
ruft den rohen binÀren Inhalt des Anhangs ab, der bereits vonbase64
oderquoted-printable
dekodiert wurde.
Dekodierung von Kodierungen und ZeichensÀtzen
Das email
-Paket leistet hervorragende Arbeit bei der automatischen Dekodierung gÀngiger Transferkodierungen (wie base64
, quoted-printable
), wenn Sie get_payload(decode=True)
aufrufen. FĂŒr den Textinhalt selbst versucht es, den im Content-Type
-Header angegebenen charset
zu verwenden. Wenn kein Zeichensatz angegeben oder dieser ungĂŒltig ist, mĂŒssen Sie dies möglicherweise elegant handhaben.
from email.message import EmailMessage
from email import message_from_string
# Example with a potentially problematic charset
email_latin1 = """
From: legacy@example.com
To: new_system@example.com
Subject: Special characters: Ă Ă©ĂĂłĂș
Content-Type: text/plain; charset="iso-8859-1"
This message contains Latin-1 characters: Ă Ă©ĂĂłĂș
"""
msg = message_from_string(email_latin1)
if msg.is_multipart():
for part in msg.iter_parts():
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset)}")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
# Fallback to a common charset or 'latin-1' if expecting it
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
else:
payload = msg.get_payload(decode=True)
charset = msg.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset)}")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
ErklÀrung:
- Versuchen Sie immer, den im
Content-Type
-Header angegebenen Zeichensatz zu verwenden. - Verwenden Sie einen
try-except UnicodeDecodeError
-Block fĂŒr Robustheit, insbesondere beim Umgang mit E-Mails aus vielfĂ€ltigen und potenziell nicht standardmĂ€Ăigen Quellen. errors='replace'
odererrors='ignore'
kann mit.decode()
verwendet werden, um Zeichen zu behandeln, die nicht auf die Zielkodierung abgebildet werden können, und so AbstĂŒrze zu verhindern.
Fortgeschrittene Parsing-Szenarien
E-Mails aus der Praxis können sehr komplex sein, mit verschachtelten Multipart-Strukturen. Die rekursive Natur des email
-Pakets macht die Navigation durch diese Strukturen unkompliziert. Sie können is_multipart()
mit iter_parts()
kombinieren, um tief verschachtelte Nachrichten zu durchlaufen.
from email.message import EmailMessage
from email import message_from_string
def parse_email_part(part, indent=0):
prefix = " " * indent
content_type = part.get_content_type()
charset = part.get_content_charset() or 'N/A'
print(f"{prefix}Part Type: {content_type}, Charset: {charset}")
if part.is_multipart():
for subpart in part.iter_parts():
parse_email_part(subpart, indent + 1)
elif part.get_filename(): # It's an attachment
print(f"{prefix} Attachment: {part.get_filename()} (Size: {len(part.get_payload(decode=True))} bytes)")
else: # It's a regular text/html body part
payload = part.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
# print(f"{prefix} Content (first 100 chars): {decoded_content[:100]}...") # For brevity
except UnicodeDecodeError:
print(f"{prefix} Content: (Binary or undecodable text)")
complex_email_raw = """
From: complex@example.com
To: receiver@example.com
Subject: Complex Email with HTML, Plain, and Attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="outer_boundary"
--outer_boundary
Content-Type: multipart/alternative; boundary="inner_boundary"
--inner_boundary
Content-Type: text/plain; charset="utf-8"
Plain text content.
--inner_boundary
Content-Type: text/html; charset="utf-8"
<html><body><h2>HTML Content</h2></body></html>
--inner_boundary--
--outer_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="data.bin"
SGVsbG8gV29ybGQh
--outer_boundary--
"""
msg = message_from_string(complex_email_raw)
print("\n--- Traversing Complex Email Structure ---")
parse_email_part(msg)
ErklÀrung:
- Die rekursive Funktion
parse_email_part
demonstriert, wie man durch den gesamten Nachrichtenbaum navigiert und auf jeder Ebene Multipart-Teile, AnhÀnge und Textinhalte identifiziert. - Dieses Muster ist sehr flexibel, um bestimmte Inhaltstypen aus tief verschachtelten E-Mails zu extrahieren.
Erstellung vs. Parsing: Eine vergleichende Perspektive
Obwohl es sich um unterschiedliche Operationen handelt, sind Erstellung und Parsing zwei Seiten derselben Medaille: der Umgang mit MIME-Nachrichten. Das VerstÀndnis des einen hilft unweigerlich beim anderen.
Erstellung (Senden):
- Fokus: Korrektes ZusammenfĂŒgen von Headern, Inhalt und AnhĂ€ngen zu einer standardkonformen MIME-Struktur.
- Hauptwerkzeug:
email.message.EmailMessage
mit Methoden wieset_content()
,add_attachment()
,add_alternative()
,add_related()
. - Hauptherausforderungen: Sicherstellung korrekter MIME-Typen, ZeichensĂ€tze (insbesondere UTF-8 fĂŒr globale UnterstĂŒtzung), `Content-Transfer-Encoding` und ordnungsgemĂ€Ăe Header-Formatierung. Fehler können dazu fĂŒhren, dass E-Mails nicht korrekt angezeigt werden, AnhĂ€nge beschĂ€digt sind oder Nachrichten als Spam markiert werden.
Parsing (Empfangen):
- Fokus: Zerlegen eines rohen E-Mail-Byte-Streams in seine Bestandteile, Extrahieren spezifischer Header, Textinhalte und AnhÀnge.
- Hauptwerkzeug:
email.parser.BytesParser
oderemail.message_from_bytes()
, gefolgt von der Navigation durch das resultierendeEmailMessage
-Objekt mit Methoden wieis_multipart()
,iter_parts()
,get_payload()
,get_filename()
und dem Zugriff auf Header. - Hauptherausforderungen: Umgang mit fehlerhaft formatierten E-Mails, korrekte Identifizierung von Zeichenkodierungen (insbesondere wenn sie mehrdeutig sind), Umgang mit fehlenden Headern und robuste Extraktion von Daten aus vielfÀltigen MIME-Strukturen.
Eine Nachricht, die Sie mit `EmailMessage` erstellen, sollte perfekt von `BytesParser` geparst werden können. Ebenso gibt Ihnen das VerstÀndnis der beim Parsen erzeugten MIME-Struktur Einblick, wie Sie komplexe Nachrichten selbst erstellen können.
Best Practices fĂŒr die globale E-Mail-Verarbeitung mit Python
FĂŒr Anwendungen, die mit einem globalen Publikum interagieren oder vielfĂ€ltige E-Mail-Quellen verarbeiten, sollten Sie diese bewĂ€hrten Verfahren berĂŒcksichtigen:
- Standardisierung auf UTF-8: Verwenden Sie immer UTF-8 fĂŒr alle Textinhalte, sowohl bei der Erstellung als auch bei der Erwartung wĂ€hrend des Parsens. Dies ist der globale Standard fĂŒr die Zeichenkodierung und vermeidet Mojibake (verstĂŒmmelten Text).
- E-Mail-Adressen validieren: Validieren Sie vor dem Senden die E-Mail-Adressen der EmpfĂ€nger, um die Zustellbarkeit sicherzustellen. Seien Sie beim Parsen auf potenziell ungĂŒltige oder fehlerhaft formatierte Adressen in den Headern `From`, `To` oder `Cc` vorbereitet.
- Rigoros testen: Testen Sie Ihre E-Mail-Erstellung mit verschiedenen E-Mail-Clients (Gmail, Outlook, Apple Mail, Thunderbird) und Plattformen, um eine konsistente Darstellung von HTML und AnhĂ€ngen sicherzustellen. Testen Sie das Parsen mit einer breiten Palette von Beispiel-E-Mails, einschlieĂlich solcher mit ungewöhnlichen Kodierungen, fehlenden Headern oder komplexen verschachtelten Strukturen.
- Geparste Eingaben bereinigen: Behandeln Sie aus eingehenden E-Mails extrahierte Inhalte immer als nicht vertrauenswĂŒrdig. Bereinigen Sie HTML-Inhalte, um XSS-Angriffe zu verhindern, wenn Sie sie in einer Webanwendung anzeigen. Validieren Sie Dateinamen und -typen von AnhĂ€ngen, um Path-Traversal- oder andere SicherheitslĂŒcken beim Speichern von Dateien zu vermeiden.
- Robuste Fehlerbehandlung: Implementieren Sie umfassende
try-except
-Blöcke beim Dekodieren von Payloads oder beim Zugriff auf potenziell fehlende Header. Behandeln SieUnicodeDecodeError
oderKeyError
elegant. - Umgang mit groĂen AnhĂ€ngen: Achten Sie auf die GröĂe von AnhĂ€ngen, sowohl bei der Erstellung (um die Grenzwerte des Mailservers nicht zu ĂŒberschreiten) als auch beim Parsen (um ĂŒbermĂ€Ăigen Speicher- oder Festplattenverbrauch zu vermeiden). ErwĂ€gen Sie das Streamen groĂer AnhĂ€nge, wenn Ihr System dies unterstĂŒtzt.
- Verwenden Sie
email.policy
: WĂ€hlen Sie fĂŒr kritische Anwendungen explizit eine `email.policy` (z. B. `policy.SMTP`), um eine strikte Einhaltung der E-Mail-Standards zu gewĂ€hrleisten, was sich auf die Zustellbarkeit und InteroperabilitĂ€t auswirken kann. - Metadaten-Erhaltung: Entscheiden Sie beim Parsen, welche Metadaten (Header, ursprĂŒngliche Boundary-Strings) wichtig sind, um sie zu erhalten, insbesondere wenn Sie ein E-Mail-Archivierungs- oder Weiterleitungssystem erstellen.
Fazit
Pythons email
-Paket ist eine unglaublich leistungsstarke und flexible Bibliothek fĂŒr jeden, der programmatisch mit E-Mails interagieren muss. Indem Sie sowohl die Erstellung von MIME-Nachrichten als auch das robuste Parsen eingehender E-Mails beherrschen, erschlieĂen Sie die Möglichkeit, anspruchsvolle E-Mail-Automatisierungssysteme zu erstellen, E-Mail-Clients zu entwickeln, E-Mail-Daten zu analysieren und E-Mail-Funktionen in praktisch jede Anwendung zu integrieren.
Das Paket handhabt die zugrunde liegende KomplexitÀt von MIME auf durchdachte Weise, sodass sich Entwickler auf den Inhalt und die Logik ihrer E-Mail-Interaktionen konzentrieren können. Ob Sie personalisierte Newsletter an ein globales Publikum senden oder kritische Daten aus automatisierten Systemberichten extrahieren, ein tiefes VerstÀndnis des email
-Pakets wird sich als unschĂ€tzbar fĂŒr die Entwicklung zuverlĂ€ssiger, interoperabler und global ausgerichteter E-Mail-Lösungen erweisen.